Skip to content

fix(updater): apply new manifest and version pins in a single Update - #362

Merged
gmmcosta15 merged 3 commits into
devfrom
fix/updater-single-pass-update
Oct 23, 2026
Merged

gmmcosta15 merged 3 commits into
devfrom
fix/updater-single-pass-update

Conversation

@gmmcosta15

@gmmcosta15 gmmcosta15 commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Description

  • Bug Fix

One Update now applies everything a BlocksScreen release ships, including components.yaml pin or branch changes and new updater code. Previously a new Spoolman pin needed a second Update and, because of a status bug, was never applied at all.

Changes

  • executor.py: a pinned version reports 1 pending update while HEAD is off the pin (it was always 0, so a new pin never counted as dirty); remote_version describes the pin; new git_show_file (sha refs only).
  • models.py: ComponentStatus.needs_update replaces the inline dirty predicate in _update_all_locked, so the batch and the follow-up share it.
  • hooks/BlocksScreen.sh: an updater/ change writes code to the restart sentinel (the existing _apply_deferred_restart path: import self-test, then post-batch self-restart). A components.yaml change also writes the pre-update hash to ~/.cache/blockscreen/updater_followup, keeping the oldest base across chained updates.
  • components.py: load_components(bundled_text) parses a given manifest instead of the bundled file.
  • service.py: run_followup runs once after a daemon restart. It diffs the manifest at the marker hash against the loaded one, then updates the changed entries that are also dirty. BlocksScreen itself is excluded, so it cannot loop.
    • It defers while printing, while a deploy runs, or while the process lock is held.
    • It also defers while a target's remote is unreachable, up to the provisioning offline cap; the entry stays dirty for a later Update.
    • The marker is cleared before the update runs, so a failing entry never re-runs on every boot.
  • dbus_service.py: the boot loop calls the follow-up after provisioning and re-polls at the retry interval while it is deferred. A follow-up that ran also triggers the background apt sweep that the restart skipped.

Motivation

Root causes:

  • Stale manifest: components.yaml is loaded once in UpdateService.__init__, and update_all runs BlocksScreen last under the old manifest.
  • Pin never dirty: a pinned component always reported commits_behind=0.
  • No sentinel writer: nothing wrote the code restart sentinel, so new updater code was only adopted on the next deploy or reboot.

The fix follows Homebrew's update.sh: it pulls brew and the taps first, then runs brew update-report in a new process, so the new manifest is applied by new code.

Old daemons are covered too. The daemon runs from the repo, so a Release-2.0 daemon executes the new hook after staging, and its _read_clear_sentinel already handles code.

Tests

  • New unit tests:
    • pin dirty and clean
    • git_show_file sha-only guard
    • needs_update
    • run_followup: no marker, malformed marker, printing, deploy, lock, apply, one-shot on failure, nothing dirty, offline cap
    • _followup_targets: changed ∩ dirty, BlocksScreen excluded, missing base
    • dbus follow-up pass and re-poll intervals
    • BlocksScreen hook run against a real temp git repo

@gmmcosta15 gmmcosta15 self-assigned this Oct 22, 2026
@gmmcosta15
gmmcosta15 requested a review from a team October 22, 2026 09:26
@gmmcosta15 gmmcosta15 added bug Something is broken, or a fix for it updater Field updates and self-heal labels Oct 23, 2026
@gmmcosta15
gmmcosta15 merged commit a067de8 into dev Oct 23, 2026
9 checks passed
@gmmcosta15
gmmcosta15 deleted the fix/updater-single-pass-update branch October 23, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something is broken, or a fix for it updater Field updates and self-heal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants